import { SteedosFieldSummaryTypeConfig, SteedosSummaryTypeValue, SteedosSummaryDataTypeValue, SupportedSummaryFieldTypes } from './type';
import { isFormulaFieldQuotingObjectAndFields } from '../formula/core';
import _ = require('lodash')
const clone = require('clone')

export * from './type'

// TODO
// const isCodeObject = (objectApiName) => {
//     return objectApiName ? false : true
// }

export class SummaryActionHandler {
    broker: any = null;
    constructor(broker) {
        this.broker = broker;
    }

    async deleteAll(objectConfig){
        try {
            if (!objectConfig) {
                return;
            }
            // console.log(`deleteAll summary`, this.cacherKey(objectConfig.name, '*'))
            await this.broker.call('metadata.fuzzyDelete', {key: this.cacherKey(objectConfig.name, '*')}, {meta: {}})
            return true
        } catch (error) {
            this.broker.logger.error(error);
        }
        return false
    }

    async getObjectConfig(objectApiName: string) {
        const data = await this.broker.call('objects.get', { objectApiName: objectApiName })
        return data ? data.metadata : null;
    }

    /**
     * 校验summaryConfig合法性并设置其reference_to_field、data_type属性值
     * 因为getObjectConfigs拿到的对象肯定不包括被禁用和假删除的对象，所以不需要额外判断相关状态
     * @param summaryConfig 
     */
    async initSummaryConfig(summaryConfig: SteedosFieldSummaryTypeConfig, summaryObject?: any) {
        const { summary_object, field_name, object_name } = summaryConfig;
        if(!summaryObject){
            summaryObject = await this.getObjectConfig(summary_object);
            if (!summaryObject) {
                return ;
            }
        }
        const referenceToField = _.find(summaryObject.fields, (item) => {
            return item.type === "master_detail" && item.reference_to === object_name;
        });
        if (!referenceToField) {
            // throw new Error(`Can't fount a master_detail type field that reference_to the master object '${object_name}' on the summary_object '${summary_object}'.`);
            return ;
        }
        summaryConfig.reference_to_field = referenceToField.name;
        if (!summaryConfig.data_type) {
            throw new Error(`Invalid field type summary '${field_name}' on the object '${object_name}', miss data_type property.`);
        }
        const dataType = await this.getSummaryDataType(summaryConfig, summaryObject);
        if(!dataType){
            return ;
        }
        if (dataType !== summaryConfig.data_type) {
            throw new Error(`The data_type of the summary field '${field_name}' on the object '${object_name}' is incorrect, it should be '${dataType}' but is set to '${summaryConfig.data_type}'.`);
        }
        summaryConfig.data_type = dataType;
    }

    async getSummaryDataType(summaryConfig: SteedosFieldSummaryTypeConfig, summaryObject: any) {
        const { summary_object, summary_type, summary_field, field_name, object_name } = summaryConfig;
        let result: SteedosSummaryDataTypeValue;
        let needSummaryField = true;
        if (summary_type === SteedosSummaryTypeValue.COUNT) {
            // 如果是COUNT类型，则忽然掉要聚合的字段
            needSummaryField = false;
        }
        else if (!summary_field) {
            throw new Error(`You have to set a summary_field property for the field '${field_name}' of the object '${object_name}' when the summary_type is not set to 'count'.`);
        }
        if (summary_field && needSummaryField) {
            const field = summaryObject.fields[summary_field];
            if (field) {
                let fieldType = field.type;
                if (fieldType === "formula") {
                    // 要聚合的是公式，则其数据类型为公式字段的数据类型
                    // 因公式字段可能再引用当前汇总字段，所以要判断下不允许互相引用
                    fieldType = field.data_type;
                    const isQuotingTwoWay = await isFormulaFieldQuotingObjectAndFields(summary_object, summary_field, object_name, [field_name]);
                    if (isQuotingTwoWay) {
                        throw new Error(`Do not refer to each other, the field '${field_name}' of the master object '${object_name}' is summarizing a formula type summary_field '${summary_field}' of the detail object '${summary_object}', but the formula type field of the detail object exactly quoting the field of the master object, which is not allowed.`);
                    }
                }
                if (fieldType === "summary") {
                    // 要聚合的是汇总字段，则其数据类型为汇总字段的数据类型
                    // 因两个对象之前不可能互为子表关系，所以汇总字段不存在互为汇总聚合关系，不需要进一步判断它们是否互相引用
                    fieldType = field.data_type;
                }
                if (!this.isSummaryFieldTypeSupported(summary_type, fieldType)) {
                    throw new Error(`The summary data_type '${fieldType}' on the field '${field_name}' of the object '${object_name}' is not supported for the summary_type '${summary_type}' which only support these types: ${SupportedSummaryFieldTypes[summary_type]}.`);
                }
                result = <SteedosSummaryDataTypeValue>fieldType;
            }
            else {
                // throw new Error(`The summary_field '${summary_field}' is not a field of the summary_object '${summary_object}'.`);
                return 
            }
        }
        else {
            result = SteedosSummaryDataTypeValue.Number;
        }
        return result;
    }

    isSummaryFieldTypeSupported(summaryType: string, summaryFieldType: string) {
        return !!(SupportedSummaryFieldTypes[summaryType] && SupportedSummaryFieldTypes[summaryType].indexOf(summaryFieldType) > -1)
    }

    async getObjectFieldSummaryConfig(fieldConfig: any, objectConfig: any) {
        let summaryConfig: SteedosFieldSummaryTypeConfig = {
            _id: `${objectConfig.name}.${fieldConfig.name}`,
            object_name: objectConfig.name,
            field_name: fieldConfig.name,
            summary_object: fieldConfig.summary_object,
            summary_type: <SteedosSummaryTypeValue>fieldConfig.summary_type,
            data_type: fieldConfig.data_type,
            summary_field: fieldConfig.summary_field,
            summary_filters: fieldConfig.summary_filters,
            reference_to_field: null
        };
        await this.initSummaryConfig(summaryConfig);

        return summaryConfig;
    }

    async getObjectFieldsSummaryConfig(config: any, datasource: string) {
        const configs = [];
        for await (const field of _.values(config.fields)) {
            if (field.type === "summary") {
                if (datasource !== "meteor" && datasource !== "default") {
                    throw new Error(`The type of the field '${field.name}' on the object '${config.name}' can't be 'summary', because it is not in the default datasource.`);
                }
                try {
                    // 这里一定要加try catch，否则某个字段报错后，后续其他字段及其他对象就再也没有正常加载了
                    const fieldSummaryConfig = await this.getObjectFieldSummaryConfig(clone(field), config)
                    configs.push(fieldSummaryConfig);
                } catch (error) {
                    this.broker.logger.error(error);
                    console.error(error);
                }
            }
        }
        
        return configs;
    }

    async getQuoteMineSummaryConfigs(objectConfig){
        const fixedConfigs = [];
        //给引用了当前对象的汇总配置中，设置reference_to_field值并校验
        const quoteMineSummaryConfigs = await this.filterSummaryConfig("*.*", objectConfig.name);
        for await (const summaryConfig of quoteMineSummaryConfigs) {
            if(!summaryConfig.reference_to_field){
                await this.initSummaryConfig(summaryConfig, objectConfig)
                fixedConfigs.push(summaryConfig);
            }
        }
        return fixedConfigs;
    }


    /* metadata 新增 */
    //fieldApiFullName: ${objectApiName}.${fieldApiName}
    cacherKey(fieldApiFullName: string, summaryObjectApiName?: string): string{
        if(!summaryObjectApiName){
            summaryObjectApiName = "*";
        }
        return `$steedos.#summary.${fieldApiFullName}.${summaryObjectApiName}`
    }

    async addSummaryMetadata(config: any, datasource: string){
        const fieldsSummaryConfig = await this.getObjectFieldsSummaryConfig(config, datasource);
        for await (const fieldSummary of fieldsSummaryConfig) {
            await this.broker.call('metadata.add', {key: this.cacherKey(fieldSummary._id, fieldSummary.summary_object), data: fieldSummary}, {meta: {}})
        }
        const quoteMineSummaryConfigs = await this.getQuoteMineSummaryConfigs(config);
        for await (const fieldSummary of quoteMineSummaryConfigs) {
            await this.broker.call('metadata.add', {key: this.cacherKey(fieldSummary._id, fieldSummary.summary_object), data: fieldSummary}, {meta: {}})
        }
        return true;
    }

    async add(objectConfig){
        try {
            await this.addSummaryMetadata(objectConfig, objectConfig.datasource);
            return true
        } catch (error) {
            this.broker.logger.error(error);
        }
        return false
    }

    async filterSummaryConfig(fieldApiFullName, summaryObjectApiName?){
        const key = this.cacherKey(fieldApiFullName, summaryObjectApiName)
        const configs = [];
        const res = await this.broker.call('metadata.filter', {key: key}, {meta: {}})
        _.forEach(res, (item)=>{
            configs.push(item.metadata)
        })
        return configs;
    }

    async filter(ctx){
        let {objectApiName, fieldApiName} = ctx.params;
        if(!objectApiName){
            objectApiName = "*";
        }
        if(!fieldApiName){
            fieldApiName = "*";
        }
        const configs = await this.filterSummaryConfig(`${objectApiName}.${fieldApiName}`);
        return configs;
    }

    async get(ctx){
        let {fieldApiFullName} = ctx.params; 
        const res = await this.filterSummaryConfig(fieldApiFullName);
        if(res && res.length > 0){
            return res[0]
        }
    }
}